#include<iostream>
using std::endl;
using std::cout;

#include<mutex>
#include<condition_variable>
#include<queue>
#include<thread>
#include<functional>

const static size_t THREAD_NUM = 4;

template<class T>
class ThreadPool
{
  ThreadPool(size_t threadNum = THREAD_NUM)
    : _threadNum(threadNum)
  {}
public:
  static ThreadPool* GetInstance()
  {
    if(_pinst == nullptr)
    {
      static std::mutex pinstMtx;
      if(_pinst == nullptr)
      {
        std::unique_lock<std::mutex> ul(pinstMtx);
        _pinst = new ThreadPool;
      }
    }

    return _pinst;
  }
  
  void Init()
  {
    for(size_t i = 0; i < _threadNum; ++i)
    {
      std::thread(std::bind(&ThreadPool::CallBack, this)).detach(); 
    }
  }

  void PushTask(T task)
  {
    {
      std::unique_lock<std::mutex> ul(_mtx);
      _taskQueue.push(task);
    }

    _cv.notify_one();
  }

  void PopTask(T& task)
  {
    std::unique_lock<std::mutex> ul(_mtx);
    while(_taskQueue.empty()) _cv.wait(ul);

    task = _taskQueue.front();
    _taskQueue.pop();
  }

  void CallBack()
  {
    while(1)
    {
      T task;
      PopTask(task);
      
      task();
    }
  }
 
private:
  size_t _threadNum;
  std::queue<T> _taskQueue;
  std::mutex _mtx;
  std::condition_variable _cv;

  static ThreadPool* _pinst;
};

template<class T>
ThreadPool<T>* ThreadPool<T>::_pinst = nullptr;
